package log

import (
	"bytes"
	"github.com/natefinch/lumberjack"
	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
	"log"
	"os"
	"path"
	"ticketSystem/shara/config"
	"time"
)

var instance *zap.Logger

//var once sync.Once
//自定义写日志的Syncer结构体
type lumberjackWriteSyncer struct {
	*lumberjack.Logger
	buf       *bytes.Buffer
	logChan   chan []byte
	closeChan chan interface{}
	maxSize   int
}

/*
每个包可以包含任意多个 init 函数，这些函数都会在程序执行开始的时候被调用。
所有被编译器发现的 init 函数都会安排在 main 函数之前执行。init 函数用在设置包、初始化变量或其他要在程序运行前优先完成的引导工作。
这里就说的很明确了，只要能被编译器发现，都会在 main 函数之前执行，也就是不会在每次引用改包的时候都执行。
*/
//大写，在main中初始化
func Init(srvName string) {
	instance = NewLogger(srvName)
}

//唯一实列
func Instance() *zap.Logger {
	return instance
	//单例模式常用写法
	//once.Do(func() {
	//
	//  })
}

//实列化日志
func NewLogger(srvName string) *zap.Logger {
	//从自定义配置取，可自己写死
	dir := path.Join(config.LogPath)
	debug := config.Debug
	//zapcore.WriteSyncer的切片
	writers := []zapcore.WriteSyncer{newRollingFile(dir)}
	writers = append(writers, os.Stdout)
	logger, dyn := newZapLogger(debug, zapcore.NewMultiWriteSyncer(writers...))
	//同步第三方库日志
	zap.RedirectStdLog(logger)
	//开启一个协程定时检查日志级别的更改
	//30秒一次
	//打算用viper的配置来重写
	go func() {
		ticker := time.NewTicker(30 * time.Second)
		for range ticker.C {
			UpdateLogLevel(srvName, dyn, debug)
		}
	}()
	return logger
}

func newRollingFile(dir string) zapcore.WriteSyncer {
	if err := os.MkdirAll(dir, 0766); err != nil {
		log.Println("failed create log dir:", dir, ":", err)
		return nil
	}
	return newLumberjackWriteSyncer(&lumberjack.Logger{
		Filename:  path.Join(dir, "output.log"),
		MaxSize:   100, //M，按大小分割日志
		MaxAge:    7,   //day ，就日志保留天数
		LocalTime: true,
		Compress:  false,
	})
}
func newLumberjackWriteSyncer(lgg *lumberjack.Logger) *lumberjackWriteSyncer {
	ws := &lumberjackWriteSyncer{
		Logger:    lgg,
		buf:       bytes.NewBuffer([]byte{}),
		logChan:   make(chan []byte, 5000),
		closeChan: make(chan interface{}),
		maxSize:   1024,
	}
	return ws
}

//lumberjackWriteSyncer实现zapcore.WriteSyncer接口方法  Write（）,Sync（）
//newLumberjackWriteSyncer返回的就是接口类型zapcore.WriteSyncer
func (lws *lumberjackWriteSyncer) Write(p []byte) (n int, err error) {
	b := make([]byte, len(p))
	//原文
	/*
		for i, c := range bs {
				b[i] = c
			}
	*/
	//切片引用类型
	copy(b, p)
	//放入通道中是为了并发安全写日志，而不是加锁，
	//但是lumberjack.Logger最后的write还是加锁写文件
	//加个缓冲通道，可以加快速度？？？
	lws.logChan <- b
	return len(p), nil
}

//不用锁，使用lumberjackWriteSyncer的通道logChan使得日志有序写入文件故返回nil
func (lws *lumberjackWriteSyncer) Sync() error {
	//zapcore.AddSync()
	return nil
}

//把lumberjackWriteSyncer里的通道logChan里的日志写入文件
func (lws *lumberjackWriteSyncer) run() {
	//每秒检查同步一次
	ticker := time.NewTicker(1 * time.Second)
	for {
		select {
		case <-ticker.C:
			if lws.buf.Len() > 0 {
				err := lws.sync()
				if err != nil {
					log.Println("failed write lumberjack.Logger:", ":", err)
				}
			}
		case p := <-lws.logChan:
			_, err := lws.buf.Write(p)
			if err != nil {
				continue
			}
		case <-lws.closeChan:
			//这样退出后func (lws *lumberjackWriteSyncer) Write
			//还是会往logChan里放值啊！没懂这个
			_ = lws.sync()
			return
		}
	}
}

//关闭一个通道会给里面发类型零值？
func (lws *lumberjackWriteSyncer) stop() {
	close(lws.closeChan)
}

//写入文件的实际操作
func (lws *lumberjackWriteSyncer) sync() error {
	defer func() {
		lws.buf.Reset()
	}()
	_, err := lws.Logger.Write(lws.buf.Bytes())
	if err != nil {
		return err
	}
	return nil
}

//实列zap.NeW()
func newZapLogger(debug bool, output zapcore.WriteSyncer) (*zap.Logger, *zap.AtomicLevel) {
	//Encoder的配置
	enCfg := zapcore.EncoderConfig{
		TimeKey:        "@timestamp",
		LevelKey:       "level",
		NameKey:        "logger",
		CallerKey:      "caller",
		MessageKey:     "msg",
		StacktraceKey:  "stacktrace",
		EncodeCaller:   zapcore.ShortCallerEncoder,
		EncodeDuration: zapcore.NanosDurationEncoder,
		EncodeTime: func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {
			encoder.AppendString(t.Format("2006-01-02 15:04:05.000"))
		},
	}
	var encoder zapcore.Encoder
	//日志级别的原子操作
	dyn := zap.NewAtomicLevel()
	if !debug { //生产模式
		dyn.SetLevel(zap.InfoLevel)
		enCfg.EncodeLevel = zapcore.LowercaseLevelEncoder
		//实例化Encoder
		encoder = zapcore.NewConsoleEncoder(enCfg) //像控制台一行一行
		// zapcore.NewJSONEncoder(encCfg) //json格式
	} else { //开发模式
		dyn.SetLevel(zap.DebugLevel)
		enCfg.EncodeLevel = zapcore.LowercaseColorLevelEncoder
		//实例化Encoder
		encoder = zapcore.NewConsoleEncoder(enCfg)
	}
	//实例化logger
	return zap.New(zapcore.NewCore(encoder, output, dyn), zap.AddCaller()), &dyn
}

//修改日志级别
func UpdateLogLevel(srvName string, dyn *zap.AtomicLevel, debug bool) {
	originLevel := "info"
	if debug {
		originLevel = "debug"
	}
	//这个没懂，直接配置日志等级的优先？
	levelConfig := make(map[string]map[string]string)
	newLevel, ok := levelConfig[srvName]["127.0.0.1"]
	if !ok {
		newLevel, ok = levelConfig[srvName]["*"]
		if !ok {
			newLevel = originLevel
		}
	}
	newZapcoreLevel := new(zapcore.Level)
	if err := newZapcoreLevel.Set(newLevel); err != nil {
		_ = newZapcoreLevel.Set(originLevel)
	}
	//int8
	//符号 * 可以放在一个指针前，那么它将得到这个指针指向地址上所存储的值
	if dyn.Level() != *newZapcoreLevel {
		log.Println("修改日志等级：", dyn.Level().String(), "=>", newZapcoreLevel.String())
		dyn.SetLevel(*newZapcoreLevel)
	}
}
